﻿using System;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;

namespace CBox_GUI
{
    /// <summary>
    /// Class to allow monitoring of applications.
    /// </summary>
    public class CBoxCore : IDisposable
    {
        #region APIs

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateNamedPipe(string lpName, uint dwOpenMode,
                                                     uint dwPipeMode, uint nMaxInstances, uint nOutBufferSize,
                                                     uint nInBufferSize,
                                                     uint nDefaultTimeOut, IntPtr lpSecurityAttributes);

        [DllImport("kernel32.dll")]
        private static extern bool ConnectNamedPipe(IntPtr hNamedPipe, IntPtr lpOverlapped);

        [DllImport("kernel32.dll", EntryPoint = "PeekNamedPipe", SetLastError = true)]
        private static extern bool PeekNamedPipe(IntPtr handle,
                                                 byte[] buffer, uint nBufferSize, ref uint bytesRead,
                                                 ref uint bytesAvail, ref uint bytesLeftThisMessageU);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool ReadFile(IntPtr handle,
            byte[] buffer, uint toRead, ref uint read, IntPtr lpOverLapped);
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool WriteFile(IntPtr handle,
            byte[] buffer, uint count, ref uint written, IntPtr lpOverlapped);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr hObject);

        #endregion

        #region Constants

        private const uint PIPE_UNLIMITED_INSTANCES = 255;
        private const uint NMPWAIT_WAIT_FOREVER = 0xffffffff;

        [Flags]
        public enum PipeOpenModeFlags : uint
        {
            PIPE_ACCESS_DUPLEX = 0x00000003,
            PIPE_ACCESS_INBOUND = 0x00000001,
            PIPE_ACCESS_OUTBOUND = 0x00000002,
            FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000,
            FILE_FLAG_WRITE_THROUGH = 0x80000000,
            FILE_FLAG_OVERLAPPED = 0x40000000,
        }

        [Flags]
        public enum PipeModeFlags : uint
        {
            //One of the following type modes can be specified. The same type mode must be specified for each instance of the pipe.
            PIPE_TYPE_BYTE = 0x00000000,
            PIPE_TYPE_MESSAGE = 0x00000004,
            //One of the following read modes can be specified. Different instances of the same pipe can specify different read modes
            PIPE_READMODE_BYTE = 0x00000000,
            PIPE_READMODE_MESSAGE = 0x00000002,
            //One of the following wait modes can be specified. Different instances of the same pipe can specify different wait modes.
            PIPE_WAIT = 0x00000000,
            PIPE_NOWAIT = 0x00000001,
            //One of the following remote-client modes can be specified. Different instances of the same pipe can specify different remote-client modes.
            PIPE_ACCEPT_REMOTE_CLIENTS = 0x00000000,
            PIPE_REJECT_REMOTE_CLIENTS = 0x00000008
        }

        public enum ApiName
        {
            MessageBoxW,
            TerminateProcess,
            CreateProcess,
            WriteProcessMemory,
            ReadProcessMemory,
            ERROR_NO_API
        }

        #endregion

        //[StructLayout(LayoutKind.Sequential)]
        //public struct ApiData
        //{
        //    public string apiName;

        //    public string msg1;
        //    public string msg2;
        //    public string msg3;
        //    public string msg4;
        //    public string msg5;

        //    public string ApiName
        //    {
        //        get
        //        {
        //            return apiName;
        //        }
        //    }

        //    public string[] Params
        //    {
        //        get
        //        {
        //            return new string[] { msg1, msg2, msg3, msg4, msg5 };
        //        }
        //    }
        //}


        [StructLayout(LayoutKind.Sequential)]
        public struct ApiData
        {
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 80)]
            private byte[] apiName;

            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg1;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg2;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg3;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg4;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg5;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg6;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg7;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg8;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg9;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.AnsiBStr, SizeConst = 1024)]
            private byte[] msg10;

            public ApiName ApiName
            {
                get
                {
                    string x = Encoding.Default.GetString(apiName).Trim();
                    if (x.Contains("MessageBoxW"))
                    {
                        return ApiName.MessageBoxW;
                    }
                    if (x.Contains("TerminateProcess"))
                    {
                        return ApiName.TerminateProcess;
                    }
                    if (x.Contains("CreateProcess"))
                    {
                        return ApiName.CreateProcess;
                    }
                    if (x.Contains("WriteProcessMemory"))
                    {
                        return ApiName.WriteProcessMemory;
                    }
                    if (x.Contains("ReadProcessMemory"))
                    {
                        return ApiName.ReadProcessMemory;
                    }
                    return ApiName.ERROR_NO_API;
                }
            }

            public string[] Params
            {
                get
                {
                    return new string[]
                        {
                            Encoding.Default.GetString(msg1), Encoding.Default.GetString(msg2),
                            Encoding.Default.GetString(msg3), Encoding.Default.GetString(msg4),
                            Encoding.Default.GetString(msg5), Encoding.Default.GetString(msg6),
                            Encoding.Default.GetString(msg7), Encoding.Default.GetString(msg8),
                            Encoding.Default.GetString(msg9), Encoding.Default.GetString(msg10)
                        };
                }
            }
        }

        public class ProcessAttachedEvent : EventArgs
        {
            public string ProcessName;
            public int Pid;

            public ProcessAttachedEvent(string processName, int pId)
            {
                ProcessName = processName;
                Pid = pId;
            }
        }

        public class ApiCalledEvent : EventArgs
        {
            public ApiData ApiData;

            public ApiCalledEvent(ApiData data)
            {
                ApiData = data;
            }
        }

        private string _processName, _dllPath;
        private IntPtr _hSend, _hRecv;
        private int _structSize;

        public delegate void AttachedHandler(object o, ProcessAttachedEvent e);

        /// <summary>
        /// Occurs when CBox is successfully attached with the target process.
        /// </summary>
        public event AttachedHandler Attached;
        protected virtual void OnAttached(ProcessAttachedEvent e)
        {
            if (Attached != null)
                Attached(this, e);
        }

        public delegate void ApiCalledHandler(object o, ApiCalledEvent e);

        /// <summary>
        /// Occurs when an API is called by the target process.
        /// </summary>
        public event ApiCalledHandler ApiCalled;
        protected virtual void OnApiCalled(ApiCalledEvent e)
        {
            if (ApiCalled != null)
                ApiCalled(this, e);
        }

        public CBoxCore(string processName, string dllPath)
        {
            _processName = processName;
            _dllPath = dllPath;

            _structSize = Marshal.SizeOf(typeof (ApiData));
        }

        /// <summary>
        /// Initializes CBox connection with the target process.
        /// </summary>
        public void Initialize()
        {
            try
            {
                _hSend = CreateNamedPipe("\\\\.\\pipe\\ChintuR", (uint)PipeOpenModeFlags.PIPE_ACCESS_DUPLEX,
                                         (uint)(PipeModeFlags.PIPE_TYPE_BYTE | PipeModeFlags.PIPE_WAIT),
                                         PIPE_UNLIMITED_INSTANCES, 2, 0, NMPWAIT_WAIT_FOREVER, IntPtr.Zero);
                _hRecv = CreateNamedPipe("\\\\.\\pipe\\ChintuS", (uint)PipeOpenModeFlags.PIPE_ACCESS_DUPLEX,
                                         (uint)(PipeModeFlags.PIPE_TYPE_BYTE | PipeModeFlags.PIPE_WAIT),
                                         PIPE_UNLIMITED_INSTANCES, 2, 0, NMPWAIT_WAIT_FOREVER, IntPtr.Zero);

                if (_hSend != IntPtr.Zero && _hRecv != IntPtr.Zero)
                {
                    if (DllInject.Inject(_dllPath, _processName))
                    {
                        ProcessAttachedEvent info = new ProcessAttachedEvent(_processName, System.Diagnostics.Process.GetProcessesByName(_processName)[0].Id);
                        OnAttached(info);
                    }
                }

                var t = new Thread(Checker) { IsBackground = true };
                t.Start();
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex.InnerException);
            }
        }

        private void Checker()
        {
            while (true)
            {
                IsDataAvailable();
                Thread.Sleep(10);
            }
        }

        private void IsDataAvailable()
        {
            try
            {
                uint bytesRead = 0, avail = 0, thismsg = 0;
                if (PeekNamedPipe(_hRecv, null, 0, ref bytesRead, ref avail, ref thismsg) && avail > 0)
                {

                    byte[] buf = GetData();

                    if (buf.Length > 0)
                    {
                        IntPtr a = Marshal.AllocHGlobal(_structSize);
                        if (a != IntPtr.Zero)
                        {
                            Marshal.Copy(buf, 0, a, buf.Length);
                            ApiData data = (ApiData) Marshal.PtrToStructure(a, typeof (ApiData));
                            Marshal.FreeHGlobal(a);
                            
                            OnApiCalled(new ApiCalledEvent(data));
                        }


                        // MANUALLY COPYING!

                        //byte[] apiName = new byte[80],
                        //       msg1 = new byte[1024],
                        //       msg2 = new byte[1024],
                        //       msg3 = new byte[1024],
                        //       msg4 = new byte[1024],
                        //       msg5 = new byte[1024];

                        //Buffer.BlockCopy(buf, 0*80, apiName, 0, 80);

                        //Buffer.BlockCopy(buf, 80 + 0, msg1, 0, 1024);
                        //Buffer.BlockCopy(buf, 80 + 1024*1, msg2, 0, 1024);
                        //Buffer.BlockCopy(buf, 80 + 1024*2, msg3, 0, 1024);
                        //Buffer.BlockCopy(buf, 80 + 1024*3, msg4, 0, 1024);
                        //Buffer.BlockCopy(buf, 80 + 1024*4, msg5, 0, 1024);

                        //ApiData data = new ApiData()
                        //    {
                        //        apiName = Encoding.Default.GetString(apiName),
                        //        msg1 = Encoding.Default.GetString(msg1),
                        //        msg2 = Encoding.Default.GetString(msg2),
                        //        msg3 = Encoding.Default.GetString(msg3),
                        //        msg4 = Encoding.Default.GetString(msg4),
                        //        msg5 = Encoding.Default.GetString(msg5),
                        //    };

                        //OnApiCalled(new ApiCalledEvent(data));
                    }
                }
            }
            catch
            {

            }
        }

        private byte[] GetData()
        {
            byte[] data = new byte[_structSize]; // Size of "ApiData" structure.
            uint read = 0;
            ReadFile(_hRecv, data, (uint) data.Length, ref read, IntPtr.Zero);
            return data;
        }

        /// <summary>
        /// Executes the current API call on hold.
        /// </summary>
        /// <param name="allow">true to let the call execute, false to decline.</param>
        public void AllowCall(bool allow)
        {
           SendData(allow ? Encoding.Default.GetBytes("y") : Encoding.Default.GetBytes("n"));
        }

        private bool SendData(byte[] data)
        {
            uint written = 0;
            return WriteFile(_hSend, data, (uint) data.Length, ref written, IntPtr.Zero);
        }

        protected virtual void Dispose(bool dispose)
        {
            if (dispose)
            {
                CloseHandle(_hSend);
                CloseHandle(_hRecv);
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~CBoxCore()
        {
            Dispose();
        }

    }
}